package org.examcity.util.beanconverter;

import java.util.HashSet;
import java.util.Set;

import org.examcity.util.ReflectionUtil;

/**
 * Context shared across invocations that is passed to participating {@link Converter}s during a
 * convertion session initiated by a call to {@link ConverterManager#convert(Object, Class)}.
 * <p>
 * This class provides access to the current {@link ConverterManager} and support methods to
 * traverse object graphs correctly.
 * 
 * @author Antonio Gomes
 */
public class ConverterContext {

	private final Set<ObjectKey> markedSet = new HashSet<ObjectKey>(100);
	private final ConverterManager manager;

	public ConverterContext(ConverterManager manager) {
		this.manager = manager;
	}

	public ConverterManager getManager() {
		return manager;
	}

	public void mark(Object b) {
		if (b == null) {
			throw new IllegalArgumentException("Expected a non-null object");
		}
		markedSet.add(new ObjectKey(b));
	}

	public boolean isMarked(Object b) {
		if (b == null) {
			throw new IllegalArgumentException("Expected a non-null object");
		}
		return markedSet.contains(new ObjectKey(b));
	}

	// INNER CLASSES -------------------------------------------------------------------------------

	/**
	 * Class that represents a key for an instance of a class.
	 */
	private static class ObjectKey {

		private final String className;
		private final long hashCode;

		public ObjectKey(Object obj) {
			this.className = ReflectionUtil.getNonProxiedClass(obj.getClass()).getName();
			this.hashCode = obj.hashCode();
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((className == null) ? 0 : className.hashCode());
			result = prime * result + (int) (hashCode ^ (hashCode >>> 32));
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			ObjectKey other = (ObjectKey) obj;
			if (className == null) {
				if (other.className != null)
					return false;
			} else if (!className.equals(other.className))
				return false;
			if (hashCode != other.hashCode)
				return false;
			return true;
		}

	}
}
